using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Windows.Forms;

using Microsoft.Build.BuildEngine;
using stsdev.Builders.SourceFiles;

namespace stsdev
{

    #region "Custom Types used by SolutionBuilder"
    public enum SolutionVersionTypeEnum
    {
        VS2005,
        VS2008_30,
        VS2008_35
    }

    public enum AssemblyDeploymentTarget
    {
        WebApplication,
        GlobalAssemblyCache
    }
    public struct SolutionSafeControlEntry
    {
        public string Assembly;
        public string Namespace;
        public string TypeName;
        public string Safe;
    }

    public struct ClassResourceEntry
    {
        public string Location;
        public string FileName;
    }

    public struct SolutionAssembly
    {
        public string Location;
        public string DeploymentTarget;
        public List<SolutionSafeControlEntry> SafeControlEntries;
        public List<ClassResourceEntry> ClassResourceEntries;
    }
    #endregion

    public class SolutionBuilder
    {

        #region "Solution/Project Properties"

        private static ISolutionProvider _SolutionProvider;
        public static ISolutionProvider SolutionProvider
        {
            get { return _SolutionProvider; }
            set { _SolutionProvider = value; }
        }

        private static string _SolutionName;
        public static string SolutionName
        {
            get { return _SolutionName; }
            set { _SolutionName = value; }
        }

        private static string _SolutionDirectory;
        public static string SolutionDirectory
        {
            get { return _SolutionDirectory; }
            set { _SolutionDirectory = value; }
        }

        public static bool GenerateSharePointInstaller { get; set; }

        private static string _SolutionId;
        public static string SolutionId
        {
            get { return _SolutionId; }
            set { _SolutionId = value; }
        }

        private static SolutionVersionTypeEnum _SolutionVersionType = SolutionVersionTypeEnum.VS2005;
        public static SolutionVersionTypeEnum SolutionVersionType
        {
            get { return _SolutionVersionType; }
            set { _SolutionVersionType = value; }
        }

        private static Guid _ProjectGuid;
        public static Guid ProjectGuid
        {
            get { return _ProjectGuid; }
            set { _ProjectGuid = value; }
        }

        private static bool _ResetWebServer;
        public static bool ResetWebServer
        {
            get { return _ResetWebServer; }
            set { _ResetWebServer = value; }
        }

        private static bool _AssemblyDeployment = true;
        public static bool AssemblyDeployment
        {
            get { return _AssemblyDeployment; }
            set
            {
                _AssemblyDeployment = value;
                if (value == false)
                {
                    _SafeControlSettings = false;
                    _CasPermissions = false;
                }
            }
        }

        private static string _KeyFileSource;
        public static string KeyFileSource
        {
            get { return _KeyFileSource; }
            set
            {
                _KeyFileSource = value;
                _KeyFileName = value.Substring(value.LastIndexOf(@"\") + 1);
            }
        }

        private static string _KeyFileName;
        public static string KeyFileName
        {
            get { return _KeyFileName; }
            //set { _KeyFileName = value; }
        }

        private static bool _SafeControlSettings = false;
        public static bool SafeControlSettings
        {
            get { return _SafeControlSettings; }
            set { _SafeControlSettings = value; }
        }

        private static XmlNode _DwpFilesNode;
        public static XmlNode DwpFilesNode
        {
            get { return _DwpFilesNode; }
            set { _DwpFilesNode = value; }
        }

        private static AssemblyDeploymentTarget _DeploymentTarget = AssemblyDeploymentTarget.GlobalAssemblyCache;
        public static AssemblyDeploymentTarget DeploymentTarget
        {
            get { return _DeploymentTarget; }
            set { _DeploymentTarget = value; }
        }

        private static bool _CasPermissions = false;
        public static bool CasPermissions
        {
            get { return _CasPermissions; }
            set { _CasPermissions = value; }
        }

        private static XmlNode _CasPermissionsNode;
        public static XmlNode CasPermissionsNode
        {
            get { return _CasPermissionsNode; }
            set { _CasPermissionsNode = value; }
        }

        private static string _OutputDirectory;
        public static string OutputDirectory
        {
            get { return _OutputDirectory; }
            set { _OutputDirectory = value; }
        }


        private static string _TargetName;
        public static string TargetName
        {
            get { return _TargetName; }
            set { _TargetName = value; }
        }

        private static string _TargetPath;
        public static string TargetPath
        {
            get { return _TargetPath; }
            set
            {
                _TargetPath = value;
                Assembly asm = Assembly.LoadFile(_TargetPath);
                FullAssemblyName = asm.FullName;
            }
        }

        private static string _FullAssemblyName;
        public static string FullAssemblyName
        {
            get { return _FullAssemblyName; }
            set { _FullAssemblyName = value; }
        }

        private static List<SolutionAssembly> _Assemblies = new List<SolutionAssembly>();
        public static List<SolutionAssembly> Assemblies
        {
            get { return _Assemblies; }
            set { _Assemblies = value; }
        }

        private static string _StsAdmDeployParameters = "-immediate";
        public static string StsAdmDeployParameters
        {
            get { return _StsAdmDeployParameters; }
            set { _StsAdmDeployParameters = value; }
        }

        private static string _StsAdmUpgradeParameters = "-local";
        public static string StsAdmUpgradeParameters
        {
            get { return _StsAdmUpgradeParameters; }
            set { _StsAdmUpgradeParameters = value; }
        }

        private static string _StsAdmRetractParameters = "-immediate";
        public static string StsAdmRetractParameters
        {
            get { return _StsAdmRetractParameters; }
            set { _StsAdmRetractParameters = value; }
        }

        #endregion

        #region "Visual Studio Configurations"

        public static string[] Configurations = 
      { "DebugBuild",
        "DebugInstall",
        "DebugDeploy",
        "DebugRedeploy",
        "DebugUpgrade",
        "DebugQuickCopy",
        "DebugRetract",
        "DebugDelete",
        "ReleaseBuild"};
        #endregion

        #region "Project Assembly References List"
        private static string[] DefaultProjectAssemblyReferences = 
      { "System",
        "System.Web",
        "System.Xml",
        "Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL"};
        public static List<string> ProjectAssemblyReferences = new List<string>(DefaultProjectAssemblyReferences);
        #endregion

        #region "Solution Provider List"

        private static ISolutionProvider[] _SolutionProviders =
      { new EmptySolutionProvider(),        
        new EmptySolutionWithAssemblyProvider(),
        new ImageOnlySolutionProvider(),
        new SimpleFeatureSolutionProvider(),
        new FeatureWithApplicationPageSolutionProvider(),
        new WebPartSolutionProvider(),
        new WebPartWithCasPermissionsSolutionProvider(),
        new WebServiceProvider() };

        private static List<ISolutionProvider>
          SolutionProviders = new List<ISolutionProvider>(_SolutionProviders);

        private static void InitializeExternalSolutionProviders()
        {
            string StsDevExePath = Assembly.GetExecutingAssembly().Location;
            string StsDevDirectory = StsDevExePath.Substring(0, StsDevExePath.LastIndexOf(@"\"));
            string SolutionProvidersXmlPath = StsDevDirectory + @"\SolutionProviders.xml";

            if (File.Exists(SolutionProvidersXmlPath))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(SolutionProvidersXmlPath);
                XmlElement RootElement = doc.DocumentElement;
                foreach (XmlNode ChildNode in RootElement.ChildNodes)
                {
                    if (ChildNode.Name.Equals("Clear"))
                    {
                        SolutionProviders.Clear();
                    }
                    if (ChildNode.Name.Equals("Add"))
                    {
                        string ClassName = ChildNode.Attributes["Class"].Value;
                        string AssemblyName = ChildNode.Attributes["Assembly"].Value;
                        SolutionProviders.Add(LoadSolutionProvider(ClassName, AssemblyName));
                    }
                }
            }
        }

        private static ISolutionProvider LoadSolutionProvider(string ClassName, string AssemblyName)
        {
            Assembly asm = Assembly.Load(AssemblyName);
            Type ProviderClass = asm.GetType(ClassName);
            Type[] ConstructorArgTypes = { };
            ConstructorInfo ProviderConstructor = ProviderClass.GetConstructor(ConstructorArgTypes);
            object[] ConstructorArgs = { };
            return (ISolutionProvider)ProviderConstructor.Invoke(ConstructorArgs);
        }

        #endregion

        public static void RunCreateSolutionWizard()
        {

            InitializeExternalSolutionProviders();

            SelectSolutionType dlg = new SelectSolutionType();
            dlg.InitializeSolutionProviders(SolutionProviders);
            DialogResult result = dlg.ShowDialog();
            if (result == DialogResult.OK)
            {
                // create standard project structure
                SolutionName = dlg.GetSolutionName();
                SolutionDirectory = dlg.GetParentDirectory() + @"\" + SolutionName;
                SolutionVersionType = dlg.GetSolutionVersionType();
                SolutionProvider = dlg.GetSelectedProvider();
                KeyFileSource = dlg.GetSigningKeyFile();
                // create standard solution/project structure
                InitializeSolution();
                // call upon SolutionProvider to create solution
                SolutionProvider.AddSolutionItems();
                // fanalize building solution
                CompleteSolution();
                string SuccessMessage = "New solution " +
                                        SolutionName +
                                        " successfully created at " +
                                        SolutionDirectory;
                MessageBox.Show(SuccessMessage, "STSDEV");
            }
            else
            {
                MessageBox.Show("STSDEV Wizard Cancelled by User", "Goodbye from STSDEV");
            }

        }

        public static void InitializeSolution()
        {
            // 
            SolutionBuilder.SolutionProvider.InitializeSolution();
            // create GUID for project
            ProjectGuid = Guid.NewGuid();
            // create solution directory
            Directory.CreateDirectory(SolutionDirectory);
            Directory.SetCurrentDirectory(SolutionDirectory);
            // create DeploymentFiles directory
            Directory.CreateDirectory(Globals.DeploymentFilesFolder);
            SolutionBuilder.SolutionProvider.ProjectProvider.AddFolder(Globals.DeploymentFilesFolder);
            // create RootFiles directory
            Directory.CreateDirectory(Globals.RootFilesFolder);
            SolutionBuilder.SolutionProvider.ProjectProvider.AddFolder(Globals.RootFilesFolder);

            if (AssemblyDeployment)
            {
                // create AssemblyInfo.cs      
                AssemblyInfoBuilder.Create();

                // add Strong Name Key file
                //We do not need to set the keyfilename because it is set when the KeyFileSource value is set.
                //KeyFileName = "KeyFile.snk";
                StrongNameKeyBuilder.AddKeyFileToProject();


                // create .csproj file for initial build of assembly        
                SolutionBuilder.SolutionProvider.ProjectProvider.Create(SolutionBuilder.SolutionName, ProjectGuid);

                // build assembly so it can be loaded and inspected using reflection
                SolutionBuilder.BuildProjectAssembly();

                // set target name and path after assembly compilation
                SolutionBuilder.TargetName = SolutionBuilder.SolutionName;
                SolutionBuilder.TargetPath = SolutionBuilder.SolutionDirectory +
                                             Globals.DebugTargetPath + @"\" +
                                             SolutionBuilder.SolutionName + ".dll";
            }

            SolutionBuilder.StsAdmDeployParameters = "-immediate";
            SolutionBuilder.StsAdmUpgradeParameters = "-local";
            SolutionBuilder.StsAdmRetractParameters = "-immediate";

            if (AssemblyDeployment)
            {
                SolutionBuilder.StsAdmDeployParameters += " -allowgacdeployment";
                SolutionBuilder.StsAdmUpgradeParameters += " -allowgacdeployment";
            }
            if (CasPermissions)
            {
                SolutionBuilder.StsAdmDeployParameters += " -allowcaspolicies";
                SolutionBuilder.StsAdmUpgradeParameters += " -allowcaspolicies";
            }
            if (SafeControlSettings || DeploymentTarget == AssemblyDeploymentTarget.WebApplication)
            {
                SolutionBuilder.StsAdmDeployParameters += " -allcontenturls";
                SolutionBuilder.StsAdmRetractParameters += " -allcontenturls";
            }

            PermissionSet CasPermissionSet = null;
            if (CasPermissions && (SolutionProvider is IPartiallyTrustedSolutionProvider))
            {
                CasPermissionSet = ((IPartiallyTrustedSolutionProvider)SolutionProvider).CreatePermissionSet();
            }

            CreateDeploymentFiles(AssemblyDeployment, SafeControlSettings, DeploymentTarget, CasPermissionSet);

        }

        public static void BuildProjectAssembly()
        {
            Engine engine = null;
            string windir = Environment.GetEnvironmentVariable("WINDIR");
            if (SolutionBuilder.SolutionVersionType == SolutionVersionTypeEnum.VS2008_35)
            {
                engine = new Engine(windir + @"\Microsoft.NET\Framework\v3.5");
            }
            else
            {
                engine = new Engine(windir + @"\Microsoft.NET\Framework\v2.0.50727");
            }
            FileLogger logger = new FileLogger();
            logger.Parameters = @"logfile=BuildInfo.txt";
            engine.RegisterLogger(logger);
            // example: csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\Microsoft.SharePoint.dll" /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Web.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /debug+ /debug:full /optimize- /out:obj\Debug\@ProjectName.dll /target:library
            bool BuildSuccess = engine.BuildProjectFile(SolutionBuilder.SolutionName + ".csproj");
            engine.UnregisterAllLoggers();
        }

        public static void CreateDeploymentFiles(bool AssemblyDeployment, bool SafeControlSettings, AssemblyDeploymentTarget DeploymentTarget, PermissionSet CasPermissionSet)
        {

            // create SolutionConfig.xml
            SolutionConfigBuilder.Create(Globals.SolutionConfigPath, AssemblyDeployment, SafeControlSettings, DeploymentTarget, CasPermissionSet);
            SolutionBuilder.SolutionProvider.ProjectProvider.AddSourceFile(new SourceFile(Globals.SolutionConfigPath, SourceFileType.None));

            // load settings from SolutionConfig.xml
            LoadSolutionConfigFile();

            // create Microsoft.SharePoint.targets      
            TargetsBuilder.Create(Globals.TargetsFilePath);
            SolutionBuilder.SolutionProvider.ProjectProvider.AddSourceFile(new SourceFile(Globals.TargetsFilePath, SourceFileType.None));

            // create manifest.xml
            ManifestBuilder.Create(Globals.ManifestXmlPath, AssemblyDeployment);
            //SolutionBuilder.SolutionProvider.ProjectProvider.AddSourceFile(new SourceFile(Globals.ManifestXmlPath, SourceFileType.None));

            // create SolutionPackage.ddf
            CabDdfBuilder.Create(Globals.SolutionPackageDdfPath);
            //SolutionBuilder.SolutionProvider.ProjectProvider.AddSourceFile(new SourceFile(Globals.SolutionPackageDdfPath, SourceFileType.None));

        }

        public static void RefreshDeploymentFiles(string TargetSolutionName, string TargetSolutionDirectory, string currentConfiguration)
        {
            // initialize static properties of SolutionBuilder class
            SolutionName = TargetSolutionName;
            TargetName = TargetSolutionName;

            if (TargetSolutionDirectory.EndsWith("\""))
                SolutionDirectory = TargetSolutionDirectory.Replace("\"", "");
            else
                SolutionDirectory = TargetSolutionDirectory;

            // echo to output window      
            Console.WriteLine("Current Solution Name: " + SolutionName);
            Console.WriteLine("Current Solution Directory: " + SolutionDirectory);
            Console.WriteLine("Current Configuration: " + currentConfiguration);
            Console.WriteLine("Refreshing deployment files...");

            RefreshDeploymentFiles();
        }

        public static void RefreshDeploymentFiles()
        {

            // set current directory
            Directory.SetCurrentDirectory(SolutionDirectory);

            // load in settings from SolutionConfig.xml
            LoadSolutionConfigFile();
            string basePath = string.Empty;

            if (!string.IsNullOrEmpty(OutputDirectory))
            {
                basePath = string.Format("{0}{1}", OutputDirectory, (OutputDirectory.EndsWith(@"\") ? string.Empty : @"\"));
            }
            else
            {
                basePath = string.Format("{0}{1}{2}{3}", SolutionDirectory, (SolutionDirectory.EndsWith(@"\") ? string.Empty : @"\"), Globals.DebugTargetPath, (Globals.DebugTargetPath.EndsWith(@"\") ? string.Empty : @"\"));
            }

            if (AssemblyDeployment)
            {
                // set target path
                TargetPath = basePath + SolutionName + ".dll";
            }


            // create Manifest.xml
            Console.WriteLine("Rebuilding " + Globals.ManifestXmlPath);
            ManifestBuilder.Create(Globals.ManifestXmlPath, AssemblyDeployment, ResetWebServer);


            // create SolutionPackage.ddf
            Console.WriteLine("Rebuilding " + Globals.SolutionPackageDdfPath);
            CabDdfBuilder.Create(Globals.SolutionPackageDdfPath, basePath);
        }

        public static void LoadSolutionConfigFile()
        {
            // clear Assemblies collection
            _Assemblies.Clear();

            if (!File.Exists(Globals.SolutionConfigPath))
            {
                SolutionConfigBuilder.Create(Globals.SolutionConfigPath, true);
            }

            XmlDocument SolutionConfig = new XmlDocument();
            SolutionConfig.Load(Globals.SolutionConfigPath);

            XmlNode SolutionIdNode = SolutionConfig.SelectSingleNode(@"/Solution/SolutionId");
            _SolutionId = SolutionIdNode.InnerText;

            XmlNode SolutionNameNode = SolutionConfig.SelectSingleNode(@"/Solution/SolutionName");
            _SolutionName = SolutionNameNode.InnerText;

            XmlNode ResetWebServerNode = SolutionConfig.SelectSingleNode(@"/Solution/ResetWebServer");
            _ResetWebServer = Convert.ToBoolean(ResetWebServerNode.InnerText);

            XmlNode AssemblyDeploymentNode = SolutionConfig.SelectSingleNode(@"/Solution/AssemblyDeployment");
            _AssemblyDeployment = Convert.ToBoolean(AssemblyDeploymentNode.InnerText);


            XmlNode SafeControlSettingsNode = SolutionConfig.SelectSingleNode(@"/Solution/SafeControlSettings");
            _SafeControlSettings = Convert.ToBoolean(SafeControlSettingsNode.InnerText);

            XmlNode CasPermissionsEnabledNode = SolutionConfig.SelectSingleNode(@"/Solution/CasPermissions");
            _CasPermissions = Convert.ToBoolean(CasPermissionsEnabledNode.InnerText);

            if (_AssemblyDeployment)
            {
                XmlNode SolutionAssembliesNode = SolutionConfig.SelectSingleNode(@"/Solution/Assemblies");
                foreach (XmlNode SolutionAssemblyNode in SolutionAssembliesNode.ChildNodes)
                {
                    SolutionAssembly asm;// = new SolutionAssembly();
                    asm.Location = SolutionAssemblyNode.Attributes["Location"].Value;
                    asm.DeploymentTarget = SolutionAssemblyNode.Attributes["DeploymentTarget"].Value;
                    asm.SafeControlEntries = new List<SolutionSafeControlEntry>();
                    asm.ClassResourceEntries = new List<ClassResourceEntry>();
                    foreach (XmlNode SafeControlEntriesNode in SolutionAssemblyNode.ChildNodes)
                    {
                        foreach (XmlNode SafeControlEntryNode in SafeControlEntriesNode.ChildNodes)
                        {
                            if (SafeControlEntryNode.Name == "SafeControl")
                            {
                                SolutionSafeControlEntry sce;// = new SolutionSafeControlEntry();
                                sce.Assembly = SafeControlEntryNode.Attributes["Assembly"].Value;
                                sce.Namespace = SafeControlEntryNode.Attributes["Namespace"].Value;
                                sce.TypeName = SafeControlEntryNode.Attributes["TypeName"].Value;
                                sce.Safe = SafeControlEntryNode.Attributes["Safe"].Value;
                                asm.SafeControlEntries.Add(sce);
                            }
                            else
                            {
                                ClassResourceEntry cre;// = new ClassResourceEntry();
                                cre.Location = SafeControlEntryNode.Attributes["Location"].Value;
                                cre.FileName = SafeControlEntryNode.Attributes["FileName"].Value;
                                asm.ClassResourceEntries.Add(cre);
                            }
                        }
                    }
                    _Assemblies.Add(asm);
                }
            }

            if (_CasPermissions)
            {
                CasPermissionsNode = SolutionConfig.SelectSingleNode(@"/Solution/CodeAccessSecurity/PolicyItem/PermissionSet");
            }

            DwpFilesNode = SolutionConfig.SelectSingleNode(@"/Solution/DwpFiles");
        }

        public static void CompleteSolution()
        {
            // create project file
            SolutionBuilder.SolutionProvider.ProjectProvider.Create(SolutionBuilder.SolutionName, ProjectGuid);
            // create solution file
            SolutionFileBuilder.Create(SolutionBuilder.SolutionName, ProjectGuid);
            // refresh depoyment files
            SolutionBuilder.RefreshDeploymentFiles();
        }

    }
}
